package com.zhiyou10.crowdfunding.util;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;


/**
 * 　1．号码的结构 
　　		公民身份号码是特征组合码，由十七位数字本体码和一位校验码组成。排列顺序从左至右依次为：六位数字地址码，
		八位数字出生日期码，三位数字顺序码和一位数字校验码。 
　　2．地址码 
　　		表示编码对象常住户口所在县（市、旗、区）的行政区划代码，按GB/T2260的规定执行。 
　　3．出生日期码 
　　		表示编码对象出生的年、月、日，按GB/T7408的规定执行，年、月、日代码之间不用分隔符。 
　　4．顺序码 
　　		表示在同一地址码所标识的区域范围内，对同年、同月、同日出生的人编定的顺序号，顺序码的奇数分配给男性，偶数分配给女性。 
　　5．校验码
　　		根据前面十七位数字码，按照ISO 7064:1983.MOD 11-2校验码计算出来的检验码。
地址码
华北地区： 北京市|110000，天津市|120000，河北省|130000，山西省|140000，内蒙古自治区|150000，
东北地区： 辽宁省|210000，吉林省|220000，黑龙江省|230000，
华东地区： 上海市|310000，江苏省|320000，浙江省|330000，安徽省|340000，福建省|350000，江西省|360000，山东省|370000，
华中地区： 河南省|410000，湖北省|420000，湖南省|430000，
华南地区： 广东省|440000，广西壮族自治区|450000，海南省|460000，
西南地区： 四川省|510000，贵州省|520000，云南省|530000，西藏自治区|540000，重庆市|500000，
西北地区： 陕西省|610000，甘肃省|620000，青海省|630000，宁夏回族自治区|640000，新疆维吾尔自治区|650000，
特别地区：台湾地区(886)|710000，香港特别行政区（852)|810000，澳门特别行政区（853)|820000


中国大陆居民身份证号码中的地址码的数字编码规则为：
第一、二位表示省（自治区、直辖市、特别行政区）。
第三、四位表示市（地级市、自治州、盟及国家直辖市所属市辖区和县的汇总码）。
	其中，01-20，51-70表示省直辖市；21-50表示地区（自治州、盟）。
第五、六位表示县（市辖区、县级市、旗）。01-18表示市辖区或地区（自治州、盟）辖县级市；
	21-80表示县（旗）；81-99表示省直辖县级市。
	
生日期码
	（身份证号码第七位到第十四位）表示编码对象出生的年、月、日，其中年份用四位数字表示，年、月、日之间不用分隔符。
	例如：1981年05月11日就用19810511表示。
顺序码
	（身份证号码第十五位到十七位）地址码所标识的区域范围内，对同年、月、日出生的人员编定的顺序号。
	其中第十七位奇数分给男性，偶数分给女性。
校验码
	作为尾号的校验码，是由号码编制单位按统一的公式计算出来的，如果某人的尾号是0-9，都不会出现X，
	但如果尾号是10，那么就得用X来代替，因为如果用10做尾号，那么此人的身份证就变成了19位，
	而19位的号码违反了国家标准，并且中国的计算机应用系统也不承认19位的身份证号码。
	Ⅹ是罗马数字的10，用X来代替10，可以保证公民的身份证符合国家标准。
	
号码含义编辑
	居民身份证是国家法定的证明公民个人身份的证件。为了堵塞和制止假居民身份证的流通和使用，
	在查验和核查居民身份证时可掌握以下几个要点：
	有效期限
		居民身份证的有效期限分为5年、10年、20年、长期四种。16岁以下的，发给有效期为5年的居民身份证；
		16周岁至25周岁的，发给有效期为10年的居民身份证；26周岁至45周岁的，发给有效期为20年的居民身份证；
		46周岁以上的，发给长期有效的居民身份证。证件有效期限从签发之日起计算。
		如某人1949年9月20日出生，1984年35周岁时申领居民身份证，签发日期为1984年12月31日，
		他属于26至45周岁这一年龄段，证件有效期限属于20年这一档次，到2004年12月30日有效期满。
		查验或检查时，应对照检查证件有效期限与持证人年龄，签发日期三者之间的关系。


	编号识别
	1、身份证编码规则如下：根据〖中华人民共和国国家标准GB11643-1999〗中有关公民身份号码的规定，公民身份号码是特征组合码，
	      由十七位数字本体码和一位数字校验码组成。
		顺序码（身份证第十五位到十七位）是县、区级政府所辖派出所的分配码，每个派出所分配码为10个连续号码，
			例如“000-009”或“060-069”，其中单数为男性分配码，双数为女性分配码，如遇同年同月同日有两人以上时顺延第二、第三、
			第四、第五个分配码。如：005的就是个男生，而且和他同年月日生的男生至少有两个，他们的后四位是001*和003*。
			分配顺序码中“999、998、997、996”四个顺序号分别为男女性百岁以上老人专用的特定编号。
		校验码（身份证最后一位）是根据前面十七位数字码，按照ISO7064:1983.MOD11-2校验码计算出来的检验码。
	2、从1999年10月1日起，全国实行公民身份证号码制度，居民身份证编号由原15位升至18位。前6位为地址码；
	      第七位至14位为出生日期码，此码由6位数改为8位数，其中年份用4位数表示；第15位至17位为顺序码，
	      取消了顺序码中对百岁老人使用的特定编号；第十八位为校验码，主要是为了校验计算机输入公民身份证号码的前17位数字是否正确，
	      其取值范围是0至10，当值等于10时，用罗马数字符X表示。
		计算方法
		1、将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为：
			7－9－10－5－8－4－2－1－6－3－7－9－10－5－8－4－2。
		2、将这17位数字和系数相乘的结果相加。
		3、用加出来和除以11，看余数是多少？
		4、余数只可能有0－1－2－3－4－5－6－7－8－9－10这11个数字。其分别对应的最后一位身份证的号码为
			1－0－X －9－8－7－6－5－4－3－2。(即余数0对应1，余数1对应0，余数2对应X...)
		5、通过上面得知如果余数是3，就会在身份证的第18位数字上出现的是9。如果对应的数字是2，身份证的最后一位号码就是罗马数字x。
		例如：某男性的身份证号码为【53010219200508011x】， 我们看看这个身份证是不是合法的身份证。
		首先我们得出前17位的乘积和【(5*7)+(3*9)+(0*10)+(1*5)+(0*8)+(2*4)+(1*2)+(9*1)+(2*6)+(0*3)+(0*7)+(5*9)+(0*10)
		+(8*5)+(0*8)+(1*4)+(1*2)】是189，然后用189除以11得出的结果是189/11=17----2，也就是说其余数是2。
		最后通过对应规则就可以知道余数2对应的检验码是X。所以，可以判定这是一个正确的身份证号码。
 */


public class IDCardUtil {
	final static Map<Integer, String> zoneNum = new HashMap<>();
	static {
		zoneNum.put(11, "北京");
		zoneNum.put(12, "天津");
		zoneNum.put(13, "河北");
		zoneNum.put(14, "山西");
		zoneNum.put(15, "内蒙古");
		zoneNum.put(21, "辽宁");
		zoneNum.put(22, "吉林");
		zoneNum.put(23, "黑龙江");
		zoneNum.put(31, "上海");
		zoneNum.put(32, "江苏");
		zoneNum.put(33, "浙江");
		zoneNum.put(34, "安徽");
		zoneNum.put(35, "福建");
		zoneNum.put(36, "江西");
		zoneNum.put(37, "山东");
		zoneNum.put(41, "河南");
		zoneNum.put(42, "湖北");
		zoneNum.put(43, "湖南");
		zoneNum.put(44, "广东");
		zoneNum.put(45, "广西");
		zoneNum.put(46, "海南");
		zoneNum.put(50, "重庆");
		zoneNum.put(51, "四川");
		zoneNum.put(52, "贵州");
		zoneNum.put(53, "云南");
		zoneNum.put(54, "西藏");
		zoneNum.put(61, "陕西");
		zoneNum.put(62, "甘肃");
		zoneNum.put(63, "青海");
		zoneNum.put(64, "新疆");
		zoneNum.put(71, "台湾");
		zoneNum.put(81, "香港");
		zoneNum.put(82, "澳门");
		zoneNum.put(91, "外国");
	}


	final static int[] PARITYBIT = { '1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' };
	final static int[] POWER_LIST = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };


	/**
	 * 身份证验证
	 * 
	 * @param certNo
	 *            号码内容
	 * @return 是否有效 null和"" 都是false
	 */
	public static boolean isIDCard(String certNo) {
		if (certNo == null || (certNo.length() != 15 && certNo.length() != 18))
			return false;
		final char[] cs = certNo.toUpperCase().toCharArray();
		// 校验位数
		int power = 0;
		for (int i = 0; i < cs.length; i++) {
			if (i == cs.length - 1 && cs[i] == 'X')
				break;// 最后一位可以 是X或x
			if (cs[i] < '0' || cs[i] > '9')
				return false;
			if (i < cs.length - 1) {
				power += (cs[i] - '0') * POWER_LIST[i];
			}
		}


		// 校验区位码
		if (!zoneNum.containsKey(Integer.valueOf(certNo.substring(0, 2)))) {
			return false;
		}


		// 校验年份
		String year = certNo.length() == 15 ? getIdcardCalendar() + certNo.substring(6, 8) : certNo.substring(6, 10);


		final int iyear = Integer.parseInt(year);
		if (iyear < 1900 || iyear > Calendar.getInstance().get(Calendar.YEAR))
			return false;// 1900年的PASS，超过今年的PASS


		// 校验月份
		String month = certNo.length() == 15 ? certNo.substring(8, 10) : certNo.substring(10, 12);
		final int imonth = Integer.parseInt(month);
		if (imonth < 1 || imonth > 12) {
			return false;
		}


		// 校验天数
		String day = certNo.length() == 15 ? certNo.substring(10, 12) : certNo.substring(12, 14);
		final int iday = Integer.parseInt(day);
		if (iday < 1 || iday > 31)
			return false;


		// 校验"校验码"
		return certNo.length() == 15 || cs[cs.length - 1] == PARITYBIT[power % 11];
	}


	private static int getIdcardCalendar() {
		GregorianCalendar curDay = new GregorianCalendar();
		int curYear = curDay.get(Calendar.YEAR);
		return Integer.parseInt(String.valueOf(curYear).substring(2));
	}
	
	public static void main(String[] args) {
		// 440101199309157968 贾飞柏 女	广东省广州市市辖区
		System.out.println(isIDCard("440101199309157968"));
		System.out.println(isIDCard("410101199510108392"));
	}


}
